
#ifndef __GOBANG_HPP__
#define __GOBANG_HPP__

#include "utils.hpp"
#include "data_manager.hpp"
#include "log.hpp"
#include "match.hpp"
#include "online_user.hpp"
#include "session.hpp"

// 编写五子棋对战的服务端
// 私有成员: 数据库句柄 , 游戏房间句柄 , 匹配游戏句柄 , 在线用户管理句柄 , 会话管理句柄 , 服务器

const std::string web_root = "./webroot";

class Gobang_server
{
    // void http_callback(websocketpp::connection_hdl hdl)
    // {
    //     // 先进行一个简单的测试
    //     // 1. 获取链接
    //     // 2. 获取请求
    //     // 3. 获取请求中的uri
    //     // 4. 将对应的HTML文件内容读取出来, 进行客户端的返回
    //     websocket_server::connection_ptr conn_ptr = _web_svr.get_con_from_hdl(hdl);
    //     websocketpp::http::parser::request req = conn_ptr->get_request();

    //     std::string uri = req.get_uri();
    //     std::string path = web_root + uri;
    //     std::string body;
    //     File_util::readfile(path , body);
    //     conn_ptr->set_status(websocketpp::http::status_code::ok);
    //     conn_ptr->set_body(body);
    // }

    void file_handle(websocket_server::connection_ptr &conn_ptr)
    {
        // 对获取静态资源的请求进行处理
        // 1. 获取uri, 并拼接上根目录
        //    1.2. 如果uri是目录, 就只能是根目录
        //    2.2  如果uri是普通文件,就直接返回内容

        websocketpp::http::parser::request req = conn_ptr->get_request();

        std::string uri = req.get_uri();
        std::string path = web_root + uri;
        if (path.back() == '/')
            path += "login.html";

        std::string body;
        bool ret = File_util::readfile(path, body);
        if (!ret)
        {
            // 构建请求直接进行返回
            body = "没有找到要进行访问的文件信息";
            conn_ptr->set_status(websocketpp::http::status_code::not_found);
            conn_ptr->set_body(body);
            return;
        }
        conn_ptr->set_body(body);
        conn_ptr->set_status(websocketpp::http::status_code::ok);
    }

    void build_resp(websocket_server::connection_ptr &conn_ptr, Json::Value &json_resp, bool result, websocketpp::http::status_code::value value)
    {
        json_resp["result"] = result;
        std::string str_resp;
        Json_util::serialization(json_resp, str_resp);
        conn_ptr->set_status(value);
        conn_ptr->append_header("Content-Type", "application/json");
        conn_ptr->append_header("Content-Length", std::to_string(str_resp.size()));
        conn_ptr->set_body(str_resp);
    }

    // 进行注册
    void reg_handle(websocket_server::connection_ptr &conn_ptr)
    {
        // 1. 获取请求正文
        // 2. 对正文进行json反序列化 , 得到用户名和密码
        // 3. 向数据库中进行插入, 实现新增用户
        // 4. 为用户返回信息

        websocketpp::http::parser::request req = conn_ptr->get_request();
        std::string body = req.get_body();
        Json::Value json_body, json_resp;
        bool ret = Json_util::unserialization(body, json_body);
        if (!ret)
        {
            json_resp["reason"] = "格式化内容不正确";
            build_resp(conn_ptr, json_resp, false, websocketpp::http::status_code::bad_request);
            return;
        }

        if (json_body["username"].isNull() || json_body["password"].isNull())
        {
            json_resp["reason"] = "用户名/密码不完整";
            build_resp(conn_ptr, json_resp, false, websocketpp::http::status_code::bad_request);
            return;
        }

        ret = _user_table.insert(json_body);
        if (!ret)
        {
            json_resp["reason"] = "用户名已存在";
            build_resp(conn_ptr, json_resp, false, websocketpp::http::status_code::bad_request);
            return;
        }
        build_resp(conn_ptr, json_resp, true, websocketpp::http::status_code::ok);
    }

    void login_handle(websocket_server::connection_ptr &conn_ptr)
    {
        // 实现登录逻辑
        // 1. 获取请求request, 进行反序列化、
        // 2. 进行登录
        // 3. 建立session会话

        websocketpp::http::parser::request req = conn_ptr->get_request();
        std::string str_body = req.get_body();
        Json::Value json_body, json_resp;
        bool ret = Json_util::unserialization(str_body, json_body);
        if (!ret)
        {
            json_resp["reason"] = "反序列化错误";
            build_resp(conn_ptr, json_resp, false, websocketpp::http::status_code::internal_server_error);
            return;
        }
        if (json_body["username"].isNull() || json_body["password"].isNull())
        {
            json_resp["reason"] = "账号/密码不完整";
            build_resp(conn_ptr, json_resp, false, websocketpp::http::status_code::bad_request);
            return;
        }

        ret = _user_table.login(json_body);
        if (!ret)
        {
            json_resp["reason"] = "密码错误";
            build_resp(conn_ptr, json_resp, false, websocketpp::http::status_code::bad_request);
            return;
        }

        std::shared_ptr<Session> ss_ptr = _sessions.create_session(json_body["uid"].asUInt64());
        if(!ss_ptr)
        {
            return ;
        }
        uint64_t ssid = ss_ptr->get_session_id();
        _sessions.set_session_expire_time(ssid, SESSION_NORMAL);

        conn_ptr->append_header("Set-Cookie", "SSID=" + std::to_string(ssid));
        build_resp(conn_ptr, json_resp, true, websocketpp::http::status_code::ok);
    }

    bool get_ssid_by_conn_ptr(websocket_server::connection_ptr &conn_ptr, uint64_t &ssid, Json::Value json_resp)
    {
        websocketpp::http::parser::request req = conn_ptr->get_request();

        std::string cookie = req.get_header("Cookie");
        if (cookie.empty())
        {
            // 没有Cookie字段, 重新进行登录

            json_resp["reason"] = "没有找到用户信息 , 请重新登录";
            build_resp(conn_ptr, json_resp, false, websocketpp::http::status_code::bad_request);
            return false;
        }

        std::vector<std::string> cookie_array;
        std::string session_id_str;
        int n = String_util::spilt(cookie, "; ", cookie_array);
        for (int i = 0; i < n; i++)
        {
            std::vector<std::string> each_cookie;
            String_util::spilt(cookie_array[i], "=", each_cookie);
            if (each_cookie[0] == "SSID")
            {
                session_id_str = each_cookie[1];
                break;
            }
        }
        if (session_id_str.empty())
        {
            // 没有找到session字段 , 重新登录
            json_resp["reason"] = "用户未进行登录 , 请重新登录";
            build_resp(conn_ptr, json_resp, false, websocketpp::http::status_code::bad_request);
            return false;
        }

        ssid = (uint64_t)std::stol(session_id_str);
        return true;
    }

    void get_info_handle(websocket_server::connection_ptr &conn_ptr)
    {
        // 根据用户的session来获取用户信息
        // 1. 提取出用户的request
        // 2. 从request中提取出用户的sessionID
        // 3. 根据用户的sessionID将用户信息进行返回
        // 4. 刷新用户session过期时间

        Json::Value json_resp;
        uint64_t ssid;
        bool ret = get_ssid_by_conn_ptr(conn_ptr, ssid, json_resp);
        if (!ret)
        {
            return;
        }

        std::shared_ptr<Session> ss_ptr = _sessions.get_session_by_ssid(ssid);
        if(!ss_ptr)
        {
            return ;
        }
        uint64_t uid = ss_ptr->get_user_id();

        _user_table.select_by_id(uid, json_resp);
        std::string str_resp;
        Json_util::serialization(json_resp, str_resp);
        build_resp(conn_ptr, json_resp, true, websocketpp::http::status_code::ok);
        _sessions.set_session_expire_time(ssid, SESSION_NORMAL);
    }

    void http_callback(websocketpp::connection_hdl hdl)
    {
        // 对获取到的http请求根据类型进行分发处理
        // 1. 获取请求
        // 2. 从请求中获取请求类型, 获取请求的uri
        // 3. 根据uri和请求类型进行分发
        websocket_server::connection_ptr conn_ptr = _web_svr.get_con_from_hdl(hdl);
        if(!conn_ptr)
        {
            return;
        }
        websocketpp::http::parser::request req = conn_ptr->get_request();

        std::string method = req.get_method();
        std::string uri = req.get_uri();
        if (method == "POST" && uri == "/reg")
        {
            reg_handle(conn_ptr);
        }
        else if (method == "POST" && uri == "/login")
        {
            login_handle(conn_ptr);
        }
        else if (method == "GET" && uri == "/info")
        {
            get_info_handle(conn_ptr);
        }
        else
        {
            file_handle(conn_ptr);
        }
    }

    void web_send(websocket_server::connection_ptr conn_ptr, Json::Value &json_resp)
    {
        std::string str_resp;
        bool ret = Json_util::serialization(json_resp, str_resp);
        if (!ret)
        {

            // 没有找到session字段 , 重新登录
            json_resp["reason"] = "序列化失败";
            build_resp(conn_ptr, json_resp, false, websocketpp::http::status_code::bad_request);
            return;
        }
        conn_ptr->send(str_resp);
    }

    bool get_ssid_by_conn_ptr_nobulid(websocket_server::connection_ptr conn_ptr, Json::Value &json_resp, uint64_t &ssid)
    {
        websocketpp::http::parser::request req = conn_ptr->get_request();

        std::string cookie = req.get_header("Cookie");
        if (cookie.empty())
        {
            // 没有Cookie字段, 重新进行登录

            json_resp["reason"] = "没有找到用户信息 , 请重新登录";
            return false;
        }

        std::vector<std::string> cookie_array;
        std::string session_id_str;
        int n = String_util::spilt(cookie, "; ", cookie_array);
        for (int i = 0; i < n; i++)
        {
            std::vector<std::string> each_cookie;
            String_util::spilt(cookie_array[i], "=", each_cookie);
            if (each_cookie[0] == "SSID")
            {
                session_id_str = each_cookie[1];
                break;
            }
        }
        if (session_id_str.empty())
        {

            // 没有找到session字段 , 重新登录
            json_resp["reason"] = "用户未进行登录 , 请重新登录";
            return false;
        }

        ssid = (uint64_t)std::stol(session_id_str);
        return true;
    }

    void ws_open_hall(websocket_server::connection_ptr conn_ptr)
    {
        // 进入房间, 建立长连接
        // 1. 检查cookie , sessionid , 用SSID去找用户
        // 2. 判断用户是否进行重复登录了, 检查是否在房间/游戏中
        // 3. 将用户加入到游戏大厅中
        // 4. 将用户会话设置为永久
        // 5. 返回信息

        Json::Value json_resp;
        uint64_t ssid;
        bool ret = get_ssid_by_conn_ptr_nobulid(conn_ptr, json_resp , ssid);

        if (!ret)
        {
            Log_ns::Log(Log_ns::Debug) << "用户会话信息获取失败";
            web_send(conn_ptr, json_resp);  
            return;
        }

        std::shared_ptr<Session> ss_ptr = _sessions.get_session_by_ssid(ssid);
        if(!ss_ptr)
        {
            return ;
        }
        uint64_t uid = ss_ptr->get_user_id();
        if (_online_hall_manager.if_online(uid) || _online_room_manager.if_online(uid))
        {
            // 没有找到session字段 , 重新登录
            json_resp["reason"] = "用户重复登录";
            json_resp["result"] = "false";
            web_send(conn_ptr, json_resp);

            return;
        }

        _online_hall_manager.insert(uid, conn_ptr);
        _sessions.set_session_expire_time(ssid, SESSION_FOREVER);

        json_resp["result"] = "true";
        json_resp["optype"] = "hall_ready";
        web_send(conn_ptr, json_resp);
    }

    void ws_open_room(websocket_server::connection_ptr conn_ptr)
    {
        // 打开房间
        // 1. 从链接中获取sessionID 进而获取会话信息
        // 2. 检查用户是否在游戏大厅/游戏房间中, 都不应该在
        // 3. 检查用户是否已经有房间了, 应该有
        // 4. 将当前用户添加到游戏房间中
        // 5. 将session生命周期设置为永久
        // 6. 返回信息


        uint64_t ssid;
        Json::Value json_resp;
        json_resp["optype"] = "room_ready";
        bool ret = get_ssid_by_conn_ptr(conn_ptr  , ssid , json_resp);
        if(!ret)
        {
            web_send(conn_ptr, json_resp);
            return ;
        }
        std::shared_ptr<Session> ss_ptr = _sessions.get_session_by_ssid(ssid);
        if(!ss_ptr)
        {
            json_resp["optype"] = "error";
            web_send(conn_ptr, json_resp);
            return ;
        }
        uint64_t uid = ss_ptr->get_user_id();
        if(_online_hall_manager.if_online(uid) || _online_room_manager.if_online(uid))
        {
            json_resp["result"] = false;
            json_resp["reason"] = "用户进行在大厅/房间中了";
            web_send(conn_ptr , json_resp);
            return ;
        }

        std::shared_ptr<Room> room_ptr = _rooms.get_room_by_uid(uid);
        if(!room_ptr)
        {
            json_resp["result"] = false;
            json_resp["reason"] = "用户还没有创建房间";
            web_send(conn_ptr , json_resp);
            return ;
        }

        uint64_t white_player = room_ptr->Get_white_player() , black_player = room_ptr->Get_black_player();

        // 补充: 获取对手信息
        uint64_t other_id = (uid ==  white_player? black_player : white_player);
        Json::Value json_other_info;
        _user_table.select_by_id(other_id , json_other_info);

        json_resp["opponent_username"] = json_other_info["username"].asCString();
        json_resp["opponent_score"] = json_other_info["score"].asInt();
        json_resp["opponent_total_count"] = json_other_info["total_count"].asInt();
        json_resp["opponent_win_count"] = json_other_info["win_count"].asInt();

        _online_room_manager.insert(uid , conn_ptr);
        _sessions.set_session_expire_time(ssid , SESSION_FOREVER);
        json_resp["result"] = true;
        json_resp["white_player"] = white_player;
        json_resp["black_player"] = black_player;
        json_resp["uid"] = uid;
        web_send(conn_ptr , json_resp);
    }

    void open_callback(websocketpp::connection_hdl hdl)
    {
        // 处理建立长连接的请求, 一共两个: 大厅的长连接和在线房间的长连接
        // 1. 根据请求的uri类型进行分类
        websocket_server::connection_ptr conn_ptr = _web_svr.get_con_from_hdl(hdl);
        if(!conn_ptr){
            return ;
        }
        
        websocketpp::http::parser::request req = conn_ptr->get_request();
        std::string uri = req.get_uri();
        if (uri == "/hall")
        {
            ws_open_hall(conn_ptr);
        }
        else if (uri == "/room")
        {
            ws_open_room(conn_ptr);
        }
    }

    void ws_close_hall(websocket_server::connection_ptr conn_ptr)
    {

        // 1. 将玩家从游戏大厅中进行移除
        // 2. 将session恢复生命周期的管理
        uint64_t ssid;
        Json::Value json_resp;
        bool ret = get_ssid_by_conn_ptr_nobulid(conn_ptr, json_resp , ssid);
        if (!ret)
        {
            web_send(conn_ptr, json_resp);  
            return;
        }
        std::shared_ptr<Session> ss_ptr = _sessions.get_session_by_ssid(ssid);
        if(!ss_ptr)
        {
            return ;
        }
        uint64_t uid = ss_ptr->get_user_id();
        _online_hall_manager.erase(uid);
        _sessions.set_session_expire_time(ssid , SESSION_NORMAL);
    }   

    void ws_close_room(websocket_server::connection_ptr conn_ptr)
    {

        // 1. 获取用户的会话
        // 2. 将用户从在线队列中移除
        // 3. 将会话设置为定时销毁
        // 4. 将用户从游戏房间中移除
        uint64_t ssid;
        Json::Value json_resp;
        bool ret = get_ssid_by_conn_ptr_nobulid(conn_ptr, json_resp , ssid);
        if (!ret)
        {
            web_send(conn_ptr, json_resp);  
            return;
        }
        std::shared_ptr<Session> ss_ptr = _sessions.get_session_by_ssid(ssid);
        if(!ss_ptr)
        {
            return ;
        }
        uint64_t uid = ss_ptr->get_user_id();
        _online_room_manager.erase(uid);

        _sessions.set_session_expire_time(ssid , SESSION_NORMAL);

        _rooms.remove_room_user(uid);
    }

    void close_callback(websocketpp::connection_hdl hdl)
    {
        // 先获取uri根据uri来判断关闭的是哪一个长连接, 进而做出相应的处理
        websocket_server::connection_ptr conn_ptr = _web_svr.get_con_from_hdl(hdl);
        if(!conn_ptr)
        {
            return;
        }
        websocketpp::http::parser::request req = conn_ptr->get_request();
        std::string uri = req.get_uri();
        if (uri == "/hall")
        {
            ws_close_hall(conn_ptr);
        }
        else if (uri == "/room")
        {
            ws_close_room(conn_ptr);
        }
    }

    void ws_message_hall(websocket_server::connection_ptr conn_ptr ,  websocket_server::message_ptr msg)
    {
        // 1. 获取请求的sessionID
        // 2. 获取请求信息
        // 3. 对请求信息进行序列化
        // 4. 提取请求中的optype来决定是开始匹配还是停止匹配
        // 5. 返回响应
        uint64_t ssid;
        Json::Value json_resp;
        bool ret = get_ssid_by_conn_ptr_nobulid(conn_ptr, json_resp , ssid);
        if(!ret)
        {
            web_send(conn_ptr, json_resp);  
            return;
        }

        std::string str_req = msg->get_payload();
        Json::Value json_body;
        ret = Json_util::unserialization(str_req , json_body);
        if(!ret)
        {
            json_resp["result"] = false;
            json_resp["reason"] = "反序列化失败";
            web_send(conn_ptr, json_resp);  
            return;
        }

        if(json_body["optype"].isNull())
        {
            json_resp["result"] = false;
            json_resp["reason"] = "没有获取到请求";
            web_send(conn_ptr, json_resp); 
        }

        std::string optype_str = json_body["optype"].asCString();

        std::shared_ptr<Session> ss_ptr = _sessions.get_session_by_ssid(ssid);
        if(!ss_ptr)
        {
            return ;
        }
        uint64_t uid = ss_ptr->get_user_id();

        if(optype_str == "match_start")
        {
            // 加入到匹配队列中
            _match.add(uid);
            json_resp["result"] = true;
            json_resp["optype"] = "match_start";
            web_send(conn_ptr, json_resp); 
            return;
        }
        else if(optype_str == "match_stop")
        {
            // 从匹配队列中移除
            _match.del(uid);
            json_resp["result"] = true;
            json_resp["optype"] = "match_stop";
            web_send(conn_ptr, json_resp); 
            return;
        }
    }



    void ws_message_room(websocket_server::connection_ptr conn_ptr ,   websocket_server::message_ptr msg)
    {
        // 1. 获取会话
        // 2. 获取房间信息
        // 3. 将消息进行序列化
        // 4. 调用接口进行处理
        uint64_t ssid;
        Json::Value json_resp;
        bool ret = get_ssid_by_conn_ptr_nobulid(conn_ptr, json_resp , ssid);
        if(!ret)
        {
            web_send(conn_ptr, json_resp);  
            return;
        }

        std::shared_ptr<Session> ss_ptr = _sessions.get_session_by_ssid(ssid);
        if(!ss_ptr)
        {
            return ;
        }
        uint64_t uid = ss_ptr->get_user_id();
        std::shared_ptr<Room> room_ptr = _rooms.get_room_by_uid(uid);
        if(!room_ptr)
        {
            return ;
        }
        std::string str_msg = msg->get_payload();
        Json::Value json_msg;
        Json_util::unserialization(str_msg , json_msg);
        room_ptr->handle_request(json_msg);
    }


    void message_callback(websocketpp::connection_hdl hdl , websocket_server::message_ptr msg)
    {
        // 1. 先获取uri, 
        // 2. 根据uri将其分配给对应的接口进行处理
        websocket_server::connection_ptr conn_ptr = _web_svr.get_con_from_hdl(hdl);
        if(!conn_ptr)
        {
            return ;
        }
        websocketpp::http::parser::request req = conn_ptr->get_request();
        std::string uri = req.get_uri();
        if (uri == "/hall")
        {
            ws_message_hall(conn_ptr , msg);
        }
        else if (uri == "/room")
        {
            ws_message_room(conn_ptr , msg);
        }
    }

public:
    Gobang_server(const std::string &host, const std::string &user, const std::string &passwd,
                  const std::string &database, const uint16_t port)
        : _user_table(host, user, passwd, database, port),
          _rooms(&_online_hall_manager, &_online_room_manager, &_user_table),
          _match(&_online_hall_manager, &_online_room_manager, &_rooms, &_user_table),
          _sessions(&_web_svr)
    {
        _web_svr.set_access_channels(websocketpp::log::alevel::none);
        _web_svr.init_asio();
        _web_svr.set_reuse_addr(true);
        _web_svr.set_http_handler(std::bind(&Gobang_server::http_callback, this, std::placeholders::_1));
        _web_svr.set_open_handler(std::bind(&Gobang_server::open_callback, this, std::placeholders::_1));
        _web_svr.set_close_handler(std::bind(&Gobang_server::close_callback, this, std::placeholders::_1));
        _web_svr.set_message_handler(std::bind(&Gobang_server::message_callback, this, std::placeholders::_1 , std::placeholders::_2));
    }

    void run()
    {
        Log_ns::Log(Log_ns::Info) << "服务器已启动";
        _web_svr.listen(8080);
        _web_svr.start_accept();
        _web_svr.run();
    }

private:
    websocket_server _web_svr;
    User_table _user_table;
    Online_manger _online_room_manager;
    Online_manger _online_hall_manager;
    Room_manger _rooms;
    Match _match;

    Session_manger _sessions;
};

#endif